package pl.edu.pw.ee.buildcity.gg;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.DateFormatUtils;
import org.apache.log4j.Logger;
import pl.edu.pw.ee.buildcity.gg.conf.GGConfiguration;
import pl.mn.communicator.*;
import pl.mn.communicator.event.*;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import java.util.Date;

/**
 * @author mgrzechocinski
 * @since 2009-09-19
 */
public class GGService {

	private static final int GG_CONNECT_TIMEOUT_IN_SECONDS = 5;

	private static final int GG_LOGIN_TIMEOUT_IN_SECONDS = 10;

	private static final Logger log = Logger.getLogger(GGService.class);

	private GGConfiguration conf;

	private final ISession session;

	private final IConnectionService connectionService;
	private final ILoginService loginService;
	private final IMessageService messageService;

	@Resource(name = "ggCommandHandler")
	private GGCommandsHandler commandHandler;

	public GGService() {
		log.debug("Starting BuildCity Gadu-gadu Bot...");
		session = SessionFactory.createSession();

		connectionService = session.getConnectionService();
		loginService = session.getLoginService();
		messageService = session.getMessageService();

		addListeners();
	}

	@PreDestroy
	private void disconnect() {
		try {
			connectionService.disconnect();
		} catch (GGException e) {
			log.error(e.getMessage(), e);
		}
	}

	@PostConstruct
	private void connect() {
		IServer server = new Server(conf.getServer(), conf.getPort());
		try {
			connectionService.connect(server);
			waitForConnection();
			login();
		} catch (GGException e) {
			log.error(e.getMessage(), e);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
	}

	private void waitForConnection() throws GGException, InterruptedException {
		int seconds = 0;
		while (isNotReadyForLogIn()) {
			log.debug("Waiting for connection to gg server for " + ++seconds + "s.");
			Thread.sleep(1000);
			if (seconds > GG_CONNECT_TIMEOUT_IN_SECONDS) {
				throw new GGException("GG Connect " + GG_CONNECT_TIMEOUT_IN_SECONDS + "s. timeout exceeded!");
			}

		}
	}

	private boolean isNotReadyForLogIn() {
		return !connectionService.isConnected() && session.getSessionState() != SessionState.AUTHENTICATION_AWAITING;
	}

	private void login() {
		final LoginContext loginContext = new LoginContext(conf.getBotAccountUid(), conf.getBotAccountPassword());
		try {
			log.debug("Trying to log into GG account for uid " + conf.getBotAccountUid());
			loginService.login(loginContext);
			waitForLogin();
		} catch (GGException e) {
			log.error(e.getMessage(), e);
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
	}

	private void waitForLogin() throws GGException, InterruptedException {
		int seconds = 0;
		while (!loginService.isLoggedIn()) {
			log.debug("Waiting for log into gg server for " + ++seconds + "s.");
			Thread.sleep(1000);
			if (seconds > GG_LOGIN_TIMEOUT_IN_SECONDS) {
				throw new GGException("GG Connect " + GG_LOGIN_TIMEOUT_IN_SECONDS + "s. timeout exceeded!");
			}
		}
	}

	public void sendMessage(int uin, String message) throws MessageCannotBeSendException {
		try {
			OutgoingMessage outMessage = OutgoingMessage.createNewMessage(uin, message);
			messageService.sendMessage(outMessage);
			log.info("Message sent to " + uin + ": '" + message + "'");
		} catch (GGException gge) {
			throw new MessageCannotBeSendException(gge.getMessage());
		} catch (Exception e) {
			throw new MessageCannotBeSendException(e);
		}
	}

	private void addListeners() {

		session.addSessionStateListener(new SessionStateListener() {
			public void sessionStateChanged(final SessionState oldSessionState, final SessionState newSessionState) {
				log.debug("Session state changed: " + oldSessionState + "->" + newSessionState);
			}
		});

		connectionService.addConnectionListener(new ConnectionListener.Stub() {
			@Override
			public void connectionEstablished() throws GGException {
				log.info("Connection established for uid: " + conf.getBotAccountUid());

			}

			@Override
			public void connectionClosed() throws GGException {
				log.info("Connection closed for uid: " + conf.getBotAccountUid());
			}
		});

		loginService.addLoginListener(new LoginListener.Stub() {
			@Override
			public void loginOK() throws GGException {
				log.debug("Login OK.");
			}

			@Override
			public void loginFailed(final LoginFailedEvent loginFailedEvent) throws GGException {
				log.warn("Login failed: " + loginFailedEvent + ". Disconnecting from GG server...");
				disconnect();
			}
		});

		messageService.addMessageListener(new MessageListener.Stub() {
			@Override
			public void messageDelivered(int uin, int messageID, MessageStatus deliveryStatus) {
				log.debug("-----------OUTGOING----------");
				log.debug("MessageDelivered, toUser: " + String.valueOf(uin));
				log.debug("MessageDelivered, messageID: " + String.valueOf(messageID));
				log.debug("MessageDelivered, messageStatus: " + deliveryStatus);
				log.debug("-----------------------------");
			}

			@Override
			public void messageArrived(IIncommingMessage incomingMessage) {

				int uin = incomingMessage.getRecipientUin();
				String msgBody = incomingMessage.getMessageBody();

				log.debug("-----------INCOMING----------");
				log.debug("MessageArrived, from user: " + uin);
				log.debug("MessageBody: " + msgBody);
				log.debug("MessageID: " + incomingMessage.getMessageID());
				log.debug("MessageStatus: " + incomingMessage.getMessageClass());
				log.debug("MessageTime: " + incomingMessage.getMessageDate());
				log.debug("------------------------------");

				processMessageAndAnswer(uin, msgBody);
			}
		});
	}

	private void processMessageAndAnswer(int uin, String msgBody) {
		String answer;
		try {
			answer = commandHandler.processMessage(msgBody);
		} catch (Exception e) {
			answer = "Could not execute command. Exception: " + e.getMessage();
			log.error("Error while processing GG message. Exception: " + e.getMessage(), e);
		}
		if (StringUtils.isNotBlank(answer)) {
			sendMessage(uin, now() + ": " + answer);
		}
	}

	private String now() {
		return DateFormatUtils.format(new Date(), "HH:mm:ss.S");
	}

	public void setConf(GGConfiguration conf) {
		this.conf = conf;
	}
}
